home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga News 96
/
Amiga News 96.iso
/
amig_ad_os
/
laurent_faillie
/
lfcinter
/
lfcinter.txt
< prev
next >
Wrap
Text File
|
1977-12-31
|
27KB
|
855 lines
L F C I n t e r
Un mini interpréteur C.
Par Laurent FAILLIE
Pour tout renseignement écrire à
Laurent FAILLIE
"Les VUARDES"
F-74930 PERS-JUSSY
1
Présentation
But:
LFCInter est un interpréteur de langage C (réduit).
Ce programme est un FREEWARE: Il peut être diffusé librement temps que
tous les fichiers de cette distribution sont inclus, sans que mon copyright
n'ai été modifié. Toutes les modifications faites pour supprimer un buggue
ou pour porter ce code vers une autre plateforme DOIVENT ETRE CLAIREMENT
IDENTIFIEES DANS LE CODE. De plus, je vous serais reconnaissant de m'avertir
de telles modifications...
Voici donc le résultat de 6 mois de programmation, pendant mes heures de
loisir...
Le développement:
Le langage C++ a été choisi. "L'approche objet" a été utilisée lorsqu'elle
simplifiait la programmation comme pour la gestion des mots clefs ou des
erreurs. Une programmation plus classique a été utilisée lors de certaines
tâches afin de diminuer le code: Par exemple, les opérateurs des calculs,
comme '+', '-', ... auraient pu être des opérateurs surchargés pour chaque
classe de représentation des valeurs mais le code nécessaire était trop
important!
Afin de garantir la portabilité sur plusieurs plate-formes, GCC a été
utilisé (version 2.7.0 disponible sur le CD Fresh Fish 10 pour Amiga). Ce
compilateur du 'Domain Public', donc gratuit, est disponible sur toutes les
machines, du simple PC jusqu'aux plus puissantes mainframes. En plus de suivre
correctement la norme, le code généré comporte peu de buggues...
Le développement a été fait sur:
- Amiga 4000 (68040, 14Mo de RAM, DD 120 Mo, CD-ROM x4, WB et KS 3.0)
- Amiga 1000 (68010, 8Mo de RAM, DD 52 Mo, WB et KS 2.1)
- MicroVaxII (16 Mo de RAM, deux RD54, sous ultrix 4.2 avec GCC compilé à
partir des sources incluses dans le CD de la version Amiga).
Portabilité:
LFCInter a principalement été écrit pour les systèmes où les entiers et
les pointeurs ont la même taille (indispensable lors de convertions entier <->
pointeur mais aussi nécessaire aux opérateurs de comparaison)... C'est le cas
de tous les systèmes 32 bits, certains 64 bits. Comme d'habitude, le problème
se pose sur PC où certains modes de compilation autorisent des entiers sur 16
bits et des pointeurs sur 32 bits.
L'utilisation de l'option '-fatal' permet un test en temps réèl de tels
débordements et provoque une erreur en cas de perte de précision.
MICRO-SUCKER est encore passé par là !!
La compilation sur le MicroVaxII m'a permis de tester la portabilité vers
les plate-formes UNIX (aucun problème supplémentaire sur le code par rapport
à la compilation sur Amiga, sauf pour quelques fonctions comme expliqué plus
loin). Les environnements ciblés lors du développement ont toujours été
l'AmigaDOS et UNIX. La compilation sous MS-DOS par le BORLAND C++ 4.52 n'a
été nécéssaire que pour la démonstration sur un portable PC. Les modifications
nécessaires ont été faites uniquement lorsqu'elles n'entraînaient pas des
incompatibilités avec les autres environnements, sinon les portions de codes
ont été supprimés (#ifndef __BCPLUSPLUS__). De plus, n'ayant pas de machine
sous cet OS incipide et anachronique chez moi, il est évident que cette
version a été moins testée et comporte, sans doute, plus de buggues que les
autres. Sur les systèmes plus ou moins apparentés a UNIX, en utilisant GCC,
le seul problème potentiel est inexistence de certaines fonctions (par exemple
isiso() sur le VAX) ou encore des noms différents pour une même fonction
(stricmp() qui devient strcasecmp() pour les compilateurs 'POSIX compliant').
2
Structures et classes importantes
class _token {
public:
_token(const char *x);
_token();
int operator * ();
operator const char * ();
_token operator ++ ();
_token operator ++ (int);
void saute(const char *);
string obj();
bool definition();
bool interne();
int hash;
protected:
const char *ptr;
short int val;
size_t len;
private:
void construit( bool );
};
Cette classe, dont les instances peuvent être utilisées comme on le
ferait avec des pointeurs, implémente en elle-même le 'parser'. Son champ
principal
const char *ptr;
pointe sur le premier caractère de l'objet en cours d'évaluation. Il est
'protected' et, en aucun cas, il ne doit être modifié directement car lui sont
associés les champs
size_t len;
qui contient la longueur de l'objet, et
short int val;
qui contient la 'valeur symbolique' correspondante. Cette valeur entière,
définie sous forme d'énumération dans le fichier "Token.h", accélère les tests
de syntaxe par la suite. On distingue les valeurs 'smbl_id' qui indique que
l'objet est un identificateur et 'smbl_icn' pour signifier la fin du fichier
source ou que l'instance n'est pas initialisée. A chaque modification de 'ptr'
ces champs sont mis-à jour par la méthode construit(). Dans cette classe, se
trouve aussi
l'opérateur * qui renvoit la valeur symbolique associée à l'objet courant,
la méthode obj() qui retourne une 'string' contenant l'objet pointé,
idéale pour afficher les messages d'erreurs,
les opérateurs ++ qui 'incrémentent' ptr pour qu'il pointe sur l'objet
significatif suivant; par significatif, s'entend que les espaces et les
commentaires sont sautés,
la méthode saute() qui permet de ... sauter, une instruction, un bloc
d'instructions, à la fin d'un () ou d'un [].
Les méthodes definition() et interne() indiquent si l'objet pointé est un
mot clef de définition de type ('int', 'void', ...), ou s'il s'agit d'une
fonction interne (printf(), malloc(), free()),
class _rep {
public:
_repval val;
const char type() const;
_rep();
_rep(const int , const char );
_rep(const long );
_rep(const char );
_rep(void *, const char, const string vers);
string info;
bool nonnull(const char *);
protected:
char typebase;
};
Cette classe de "représentation de valeurs", contient, en plus des
valeurs en elles-mêmes, son type. Le 'type de base', c'est à dire celui qui
caractérise comment la valeur est mémorisée, est accessible par la méthode
type() et peut prendre les valeurs suivantes
- '*' : c'est un pointeur,
- 'I' : c'est un entier,
- 'C' : c'est un caractère,
- 'V' : c'est une valeur 'void',
- 'L' : c'est une valeur littérale, c'est à dire le type permettant de
stocker n'importe quelle autre valeur. Il est défini comme un 'long int'.
Pour un pointeur, le champ
string info;
contient le type de ce vers quoi il pointe. Par exemple, pour un pointeur
sur des caractères (une chaîne ?), typebase vaut '*' et info vaut "C".
struct _var {
_var(_var *, const string &, const string &, const int);
~_var();
virtual void operator = (_rep )=0;
virtual _rep repval(const char *)=0; // Retourne la valeur stockée
virtual void *refval()=0; // Retourne une reférence sur cette valeur
protected:
void veriftype(_rep &);
_var *succ;
public:
string nom; // Nom du symbole
string type; // Type de ce symbole
int h; // Hash code pour ce symbole
friend _var *trouve_symbole( const _var *, const string &, int);
};
struct _var et ces dérivées _var_int, _var_char, _var_fonc, _var_ptr
permettent de mémoriser les variables. La structure de base contient les
champs permettant d'identifier la variable (nom, type,...) ainsi que la
définition des méthodes utilisée pour obtenir la valeur mémorisée ou une
référence sur celle-ci. Chaque classe dérivée contient un champ 'val' qui est
l'espace de stockage pour le contenu de la variable. Le champ 'succ' sert à
lier les symboles ensemble (voir la structure _tablesmb) et 'h' contient le
hash code du nom pour accélérer les recherches.
class _resallouee {
void *data;
_resallouee *succ;
public:
_resallouee( _resallouee *, void * );
~_resallouee();
friend _var *ajoute_symbole( _var **, const string &,
const string &, const char *, void *, int);
};
Cette classe mémorise les différents blocs de mémoire associés à un
tableau. Ainsi, ils seront libérés lors de sa destruction. Cette classe ne
comporte que 2 champs
void *data;
pointeur sur le bloc de données, alloué avec malloc(),
_resallouee *succ;
pointeur sur la ressource suivante. En effet, pour un tableau
char x[5][10];
6 ressources ont été allouées, 5 contenant 10 caractères pour stocker les
données de la seconde dimension, plus 1 contenant 5 pointeurs pour stocker la
premiere (des tableaux de tableaux sont en fait des tableaux de pointeurs sur
les premiers éléments des dimentions inférieures !). Grâce à ce système, des
tableaux de dimension quelconque peuvent être alloués... dans la limite de la
mémoire disponible.
struct _tablesmb {
_tablesmb( _tablesmb * );
~_tablesmb();
_var *var; // premier symbole de la liste
_tablesmb *parent; // Tds précedente
};
Plutôt que d'utiliser des tables de symboles statiques comme le font la
plus part des compilateurs ou des interpréteurs, j'utilise des tables
dynamiques contituées d'une liste simplement chaînée, gérée par cette
structure. La fonction trouve_symbole() permet de trouver un symbole dans la
table courante, dans celles des blocs parents ou enfin dans la table des
symboles globaux.
template <class T> class LFDynaStack {
private:
/*
* Segments de données
*/
struct LFDSData {
LFDSData *suivant, *precedent;
T *data;
} *premier, *dernier, *courant;
/*
* Index
*/
int nbreparseg; // Nombre d'objets par segment
int maxidx; // Nombre d'objets dans la pile
int cidx; // Idx de l'objet courrant
public:
LFDynaStack(int);
~LFDynaStack();
bool Push(T);
T Pop();
T &operator[](int);
int length();
int current();
};
Cette classe implémente une pile de données homogènes. Au lieu d'allouer
les données une par une comme il faudrait le faire avec une liste doublement
chaînée, ce qui a tendance à fragmenter la mémoire (et à faire ramer les PC !),
elles sont regroupées en blocs. Le nombre d'allocation/libération étant
diminué, l'exécution des fonctions utilisant des piles, comme lors de calcul,
est accélérée...
Les blocs sont chaînés entre eux (structure LFDSData). Le champ 'cidx'
mémorise quelle est la dernière donnée accédée et 'courant' dans quel bloc
elle se trouve. Comme la majorité des accès à ce genre de pile est séquenciel,
ces champs évitent de nombreuses recherches de blocs.
Note: Lorsque l'on tente d'accéder à une donnée qui n'existe pas (index hors
limite), une donnée créée par le constructeur par défaut est renvoyé. Cette
classe dispose des méthodes et des opérateurs suivants:
Push() : Ajoute une donnée au sommet de la pile, en créant s'il le faut un
nouveau bloc,
Pop() : Retourne la dernière donnée poussée et la supprime de la pile,
operator[] : Retourne une référence sur la donnée de la pile dont l'index
est passé en argument,
current() et length(): Renvoient respectivement l'index de l'élément
courant et du dernier.
3
Comment est interprété le source?
Voici les différentes étapes effectuées lors du lancement d'LFCInter:
1/ Décodage et mémorisation des arguments de l'interpréteur,
2/ Lecture et stockage dans le buffer du fichier à interpréter,
3/ Parcours rapide du buffer à la recherche des symboles globaux qui sont
stockés dans la table globale,
4/ Construction des paramêtres de la fonction main(), argc et argv grâce aux
arguments restant dans la ligne de commande,
5/ interprétation de la fonction main(), qui provoquera l'interprétation des
fonctions qu'elle appelle...
L'interprétation est elle-même est assurée par les fonctions:
execfonc() a pour tâche principale de décoder les arguments d'une fonction
et de tester si sa valeur de retour est correcte (par exemple, pas de valeur de
retour s'il s'agit d'une fonction déclarée comme 'void'). Elle lance ensuite
execbloc() sur le bloc principale de la fonction,
execbloc() interprète un bloc. Si le premier élément lu est un '{',
l'interprétation se terminera au '}' correspondant, dans le cas contraire une
seule instruction est interprétée en lançant la fonction execinst(),
execinst() exécute une seule instruction. Pour celles qui sont répétitives,
une sorte de cache est utilisé afin de ne tester leur syntaxe qu'une seule
fois. S'il ne s'agit pas d'un mot clef du C, il s'agit d'un calcul donc eval()
est appellée (voir le § des calculs). Si un '{' est rencontré, execbloc() est
appelée,
Si dans l'expression a calculer se trouve une fonction utilisateur,
lancefonc() est appelée: Elle lira les arguments et lancera execfonc(),
Si une fonction interne est détectée, interne() est appelée: Elle contient
l'interface entre le programme source interprété et les fonctions du
compilateur. Dans la magorité des cas, il ne s'agit que de tester la validité
des arguments mais pour les fonction plus complexes comme le printf(),le code
nécessaire est plus important.
4
Les calculs
Cette B.N.F. est une adaptation de celle fournie dans la documentation du
BORLAND C++ 4.0 (entre parenthèses se trouve la priorité de chaque 'couche'.)
(-11) expression ::= exp_affect {[, exp_affect ]}
Le résultat de l'expression est la valeur de la derniere exp_affect.
(-10) exp_affect ::= exp_un opp_aff exp_affect
::= expr_cond
avec opp_aff '=','*=','/=','%=','+=','-=','&=','^=','|=','<<=','>>='
exp_un doit être une référence.
(-9) expr_cond ::= exp_OU_log ? exp_OU_log : expr_cond
::= exp_OU_log
(-8) exp_OU_log ::= exp_OU_log || exp_ET_log
::= exp_ET_log
(-7) exp_ET_log ::= exp_ET_log && exp_OU_incl
::= exp_OU_incl
(-6) exp_OU_incl ::= exp_OU_incl | exp_OU_excl
::= exp_OU_excl
(-5) exp_OU_excl ::= exp_OU_excl ^ exp_ET
::= exp_ET
(-4) exp_ET ::= exp_ET & exp_egal
::= exp_egal
(-3) exp_egal ::= exp_egal == exp_rel
::= exp_egal != exp_rel
::= exp_rel
(-2) exp_rel ::= exp_rel opp_rel exp_dcl
::= exp_dcl
avec opp_rel '<','>','<=','>='
(-1) exp_dcl ::= exp_dcl >> exp_add
::= exp_dcl << exp_add
::= exp_add
(0) exp_add ::= exp_add + exp_mul
::= exp_add - exp_mul
::= exp_mul
(1) exp_mul ::= exp_mul exp_un
::= exp_mul / exp_un
::= exp_mul % exp_un
::= exp_un
(2) exp_un ::= opp_un exp_un
::= exp_posf
avec opp_un '++','--','&','*','+','-','~','!'
(3) exp_posf ::= exp_prim
::= exp_posf++
::= exp_posf--
::= exp_posf [expr_cond]{[expr_cond]}
Valeur d'un tableau
(4) exp_prim ::= valeur_littéral
::= variable
::= fonction (...) Appel de fonction
::= ( exp_affect )
L'entête du fichier LFCI_Cal.cxx contient plus d'informations sur les
différences entre cette BNF et celle du BORLAND (généralement des erreurs dans
la BNF incluse dans la documentation de ce compilateur !).
Si cette BNF permet de décrire formellement le langage, la transposer
directement en code C est totalement inadapté à un interpréteur. Par exemple,
prenons le cas de la couche de niveau 0:
(0) exp_add ::= exp_add + exp_mul
::= exp_add - exp_mul
::= exp_mul
Si l'on suit la BNF "à la lettre", le programme doit rechercher le DERNIER
signe '+' ou '-' pour séparer 'exp_add' et 'exp_mul'. Dans le cas de
l'expression "2+5*-3", il trouvera d'abors le '-' et devra déterminer s'il
s'agit bien du '-' binaire ou du '-' unaire. Pour ce faire, il doit avoir
'parsé' tout le début de l'expression! Dans ce cas c'est non, donc le programme
recherche le symbole précédent et trouve le '+', et à nouveau, il doit à
nouveau chercher si cet opérateur est binaire. En plus, s'il n'y a aucune
optimisation, il reparsera une nouvelle fois l'expression depuis le début, et
la même chose se passera pour toute les couches... Quelle perte de temps!
Plutot que d'utiliser ce genre de code, j'ai préféré utiliser un 'remix' entre
une BNF restreinte et du code utilisant la méthode des "priorités des
opérateurs". Voici donc cette BNF restreinte, codée dans les fichiers
LFCI_Cal.cxx et LFCI_icl.cxx:
reponse ::= exp_aff {, exp_aff}
codé dans la fonction eval().
exp_aff ::= reférence {oppaff exp_cond}
exp_aff ::= exp_cond
codé dans la fonction affectation().
exp_cond ::= exp_binaire ? exp_cond : exp_cond
exp_cond ::= exp_binaire
codé dans la fonction conditionnel().
exp_binaire ::= exp_un {[opérateur_binaire exp_un]}
codé dans la fonction binaire().
exp_un ::= [{pré-opérateur}] exp_posf
codé dans la fonction lectunaire().
exp_posf ::= exp_prim [{post-opérateur}]
codé dans la fonction lectunaire().
exp_prim ::= valeur_littéral
::= variable
::= fonction (...) Appel de fonction
::= ( exp_affect )
codé dans la fonction CalcLexer().
5
Les limitations de la version actuelle
Il est évident qu'un tel projet est complexe et demande beaucoup de temps.
C'est pourquoi, cet interpréteur comporte certaines limitations par rapport au
C standard:
- Pas d'instructions du préprocesseur (ce qui est normal pour un langage
interprété !), il est cependant assez simple d'ajouter un mécanisme de #define
pour les constantes,
- Pas de prototype, ce qui est d'ailleur inutile car, dans un premier
temps, LFCInter construit une table des symboles globale, où sont incluses les
fonctions,
- il est impossible d'utiliser des fonctions qui demandent un nombre
variable d'arguments (manque de temps pour implémenter des 'va_list' qui soient
compatibles avec les autres compilateurs). La seule exception concerne les
fonctions internes comme les '?printf()'.
- Certains qualificateurs sont ignorés : "register", "volatile", "short",
"long", "const", "extern", "signed", "unsigned" et "auto".
- D'autres causent des erreurs: "float", "struct", "union", "double" et
"static" (voir le fichier "Token.cxx" qui contient la liste des mots clefs
reconnus).
- Pas de flottants (mais ils peuvent être facilement implémentés),
- les identificateurs de tableaux sont manipulés en temps que pointeur. En
réalité, tout au long des calculs, ils devraient concerver leur type 'T'
(pointeur constant) car un code du genre
void main(){
char x[25];
x++;
}
est illégal en C. De plus, en propageant le type 'T', l'opérateur sizeof()
fonctionnerait correctement sur des tableaux.
- Pas de tableaux de taille indéterminée, comme
char ok[]={'O','u','i',0};
- Pas de déclarations implicites,
- Pas d'énumérations,
- sizeof(), qui est considéré comme une fonction, n'est pas implémenté,
- je n'ai pas eu le temps de codé l'opérateur ?:,
- Si la fonction ne prend aucun argument, il est interdit de la définir
comme
int truc( void )
mais utiliser
int truc()
- '\' ne peut être utilisé comme caractère de continuation
char *x = "Salut \
tout le monde.";
est interdit. Il fait écrire
char *x = "Salut "
"tout le monde.";
Le résultat étant le même.
- Pas de structure, de goto, de typedef ou de switch()/case() (désolé !).
- Par contre, contrairement à la norme ANSI, mais comme l'autorise la
majorité des compilateurs, les commentaires peuvent être imbriqués.
- Les commentaires C++ (//) sont autorisés.
- la bibliothèques des fonctions disponibles est assez réduite : Sont
implémentées la grande majorité des fonctions de chaînes (standard ANSI), les
fonctions d'allocation dynamique de mémoire, de manipulation mémoire, quelques
fonctions systèmes comme clock(), time() ou system(). Aucune gestion de
fichiers n'est implémentée, mais la pseudo fonction (non standard)
'flushstdout()' permet de vider le tampon de sortie. L'utilisation des fichiers
par les 'files descriptors' est triviale à ajouter, comme la plus part des
fonctions manquantes dont le nombre d'arguments est fixe... mais je n'ai pas eu
le temps. L'annexe B contient toutes les fonctions reconnues.
6
Buggues connus
- Plantage sur l'Amiga 1000 si le programme est stoppé par un CTRL-C ou par
la commande Break. Je ne pense pas que ceci provienne d'LFCInter par lui-même
mais d'un buggue de la 'ixemul.library' version 41.2 lors gestion des signaux
avec un processeur 16/32 bits car GCC lui-même plante dans cette configuration
lors d'un break. Ce problème n'existe pas sur un Amiga 1200/020 ou l'Amiga
4000/040, pleinement 32 bits...
- Attention à certaines fonctions comme time() qui demandent un pointeur
sur un entier long comme argument car sur les systèmes ayant des entiers de 16
bits (PC par exemple), passer l'adresse d'un entier peut planter le système...
- Attention, votre programme en interprété possède les mêmes buggues
potentiels que s'il avait été compilé. Par exemple, à aucun moment la validité
des pointeur n'est testée. De plus, comme les fonctions d'interprétation
s'appellent récursivement, une pile importante peut être nécessaire...
- Comme je l'ai indiqué plus haut, le ms-dos n'est pas la plateforme cible
pour ce projet, c'est pourquoi les caractères accentués seront mal affichés
sous cette... (ce n'est pas de ma faute si le borland est incapable de
convertir les messages lorsque la cible est un executable DOS).
7
Conclusion
Il est évident qu'un tel projet ne peut être parfait en si peu de temps de
développement, et il doit rester encore quelques buggues. Même si LFCInter 1.0
n'est pas un produit terminé, beaucoup des objectifs sont atteints:
- Il fonctionne,
- le comportement des fichiers interprétés est très proche de celui des
versions compilées,
- un nombre substanciel de fonctions a été implémenté,
- les manques importants de cette version comme les flottants ou la gestion
des fichiers peuvent facilement être complétés,
- le portage sur différents environnements ne pose pas de gros problèmes...
La réalisation de ce projet m'a aussi apporté des connaissances nouvelles:
- réalisation d'un interpréteur, et surtout de ses fonctions pour calculer
une expression, qui est la partie du code la plus complexe,
- amélioration de mes connaissances dans certaines particularités du C,
- utilisation de techniques du C++ que je n'ai jamais eu le courage
d'utiliser précédemment comme les 'template',
- création d'un code réellement portable (et accessoirement installation de
GCC sur mon VAX).
Je tiens particulièrement à remercier BABETH et le grand LAURENT sans qui ce
texte et mes sources ne seraient qu'un ramassis ignoble de fautes
d'orthographes (même s'il y en reste quelques unes!). Merci pour leur patience...
Annexe A:
Description des fichiers sources
LFCInter.h : Définitions générales utilisées dans les autres fichiers. On
y trouve entre autres la définition de la structure _amsg qui mémorise les
options passées en arguments et certains paramètres internes qui y sont
associés...
LFCInter.cxx : C'est ce fichier qui contient la fonction main() et quelques
fonctions d'usage générale.
LFDStack.h : Implémentation d'une classe de stockage dynamique en pile
LIFO.
Token.cxx et Token.h : Contiennent la classe qui permet de parser le texte
source ainsi que l'énumération des valeurs symboliques.
LFCI_exe.cxx : Contient le code qui permet l'exécution des blocs d'
instructions, et des mots clefs du C.
LFCI_Cal.h : Définition de classes et des prototypes utilisés lors des
calculs, entre autre les représentations des valeurs (_rep), des variables
(_var et ses dérivées) et le stockage des symboles (_tablesmb).
LFCI_Cal.cxx, LFCI_icl.cxx, LFCI_icl.h : Exécution des calculs. Pour
accélérer la compilation, j'ai séparé ce code en 2 fichiers. LFCI_Cal.cxx
contient principalement les fonctions d'évaluation des expressions alors que
LFCI_icl.cxx contient l'implémentation des calculs par eux-mêmes.
LFCI_fnc.cxx : Contient le code des fonctions internes à LFCInter comme
les printf(), malloc(), free() ...
LFCI_Lex.cxx : Lecture des identificateurs et allocation de la mémoire
pour les tableaux.
LFCI_Var.cxx : Gestion des variables et des tables des symboles.
LFCI_Ver.cxx : Ce fichier ne sert qu'a fournir à LFCInter sa date de
compilation.
Annexe B:
Liste des fonctions supportées
Note: Certaines fonctions ne sont pas disponnibles sur certaines platformes
(si l'OS hôte ne les posseident pas...).
Fonctions d'entrées sorties
printf(), sprintf(), scanf(), putchar(), puts(), gets(), getchar(),
flushstdout(),
Fonctions de types
isdigit(), islower(), isspace(), ispunct(), isupper(), isalpha(), isxdigit(),
isalnum(), isprint(), isgraph(), iscntrl(), isascii(), isiso(), toupper(),
tolower(), toiso(),
Fonctions de chaînes
strcat(), strchr(), strcmp(), strcpy(), strerror(), strlen(), strncat(),
strncmp(), strncpy(), strrchr(), strdup(), stricmp(), strnicmp(), strcasecmp(),
strncasecmp(), strpbrk(), strstr(), strcoll(), strcspn(), strspn(), strtok(),
strtol(),
Fonctions de gestion de la mémoire
realloc(), malloc(), free(), swab(), memchr(), memcmp(), memcpy(), memmove(),
memset(), memccpy(),
Autres fonctions
exit(), atoi(), time(), ctime(), clock(), sleep(), system(),